package auth

import (
	"encoding/json"
	"errors"
	"fmt"
	"gitee.com/baizhige/go-server-tempalte/common/constant"
	"gitee.com/baizhige/go-server-tempalte/common/log"
	"gitee.com/baizhige/go-server-tempalte/common/redis"
	"gitee.com/baizhige/go-server-tempalte/util/assert"
	"gitee.com/baizhige/go-server-tempalte/util/random"
	"github.com/gin-gonic/gin"
)

// Login 记录用户登录状态，包括把token放到响应头
func Login(traceId string, c *gin.Context, user User) (token string, err error) {
	token = random.Crypto(16)
	config := user.GetLoginConfig()
	tokenKey := config.TokenKey
	err = redis.Set(key(tokenKey, token), user, config.Expire)
	if err != nil {
		log.WarnTF(traceId, "zabbixAlert : redis set token fail!!,err:%v", err)
		return
	}

	c.Header(tokenKey, token)
	return
}

// Logout 清理用户缓存
func Logout[T User](traceId string, c *gin.Context) (err error) {
	var user T
	config := user.GetLoginConfig()
	token := c.GetHeader(config.TokenKey)
	if token == "" {
		log.ErrorTF(traceId, "get token error: %s", config.TokenKey)
		return ErrTokenNotFInd
	}
	return LogoutByToken[T](traceId, token)
}

// LogoutByToken 通过Token清理用户缓存
func LogoutByToken[T User](traceId, token string) (err error) {
	var user T
	config := user.GetLoginConfig()
	err = redis.Del(key(config.TokenKey, token))
	if err != nil {
		log.ErrorTF(traceId, "redis Del error: %s", err)
		return err
	}
	return nil
}

// SetUser 请求授权 从Redis获取本次请求对应的用户 并且将用户放入上下文
func SetUser[T User](traceId string, c *gin.Context) (user T, err error) {
	var info T
	config := info.GetLoginConfig()
	// 获取token
	token := c.GetHeader(config.TokenKey)
	if token == "" {
		log.InfoTF(traceId, "get token error: %s", config.TokenKey)
		err = ErrTokenNotFInd
		return
	}

	// 获取用户
	redisKey := key(config.TokenKey, token)
	value, err := redis.Get(redisKey)
	if err != nil {
		log.ErrorTF(traceId, "get redis error: %s", err)
		if errors.Is(err, redis.ErrNotFound) {
			err = ErrUserLogin
		}
		return
	}

	// 解析用户
	err = json.Unmarshal([]byte(value), &info)
	if err != nil {
		log.ErrorTF(traceId, "unmarshal userCard error: %s", err)
		return
	}

	//放入上下文
	c.Set(CtxUser, info)
	return info, nil
}

// GetUser 从上下文拿用户
func GetUser[T User](traceId string, c *gin.Context) (user T, err error) {
	value, exists := c.Get(CtxUser)
	if !exists {
		err = ErrUserLogin
		return
	}

	user1, ok := value.(T)
	if ok {
		return user1, nil
	}
	err = ErrUserAssert
	return
}

// LoginRefresh 登录状态刷新，保持登录状态
func LoginRefresh[T User](traceId string, c *gin.Context) (err error) {
	var user T
	// 获取token
	config := user.GetLoginConfig()
	token := c.GetHeader(config.TokenKey)
	if token == "" {
		log.ErrorTF(traceId, "get token error: %s", config.TokenKey)
		err = ErrTokenNotFInd
		return
	}

	// 续期
	err = redis.Expire(key(config.TokenKey, token), config.Expire)
	if err != nil {
		log.ErrorTF(traceId, "Login Refresh Fail! err:%v", err)
	}
	return
}

// UserRefresh 刷新缓存用户
func UserRefresh(traceId string, c *gin.Context, user User) (err error) {
	// 获取token
	config := user.GetLoginConfig()
	token := c.GetHeader(config.TokenKey)
	if token == "" {
		log.ErrorTF(traceId, "get token error: %s", config.TokenKey)
		err = ErrTokenNotFInd
		return
	}

	// 续期
	err = redis.Set(key(config.TokenKey, token), user, config.Expire)
	if err != nil {
		log.ErrorTF(traceId, "Login User Refresh Fail! err:%v", err)
	}
	return
}

// Authentication 验证用户是否有权限,调用该方法前确保调用过SetUser将用户放入上下文
func Authentication[T User](traceId string, c *gin.Context, apiInfo Router) (err error) {
	//无需登录则放行
	if !apiInfo.Login() {
		return
	}
	//获得缓存用户
	user, err := GetUser[T](traceId, c)
	if err != nil {
		return
	}

	//无需鉴权则放行
	if !apiInfo.Auth() {
		return
	}
	authorizations := user.GetAuthorizations()
	if !assert.ArrExists(authorizations, apiInfo.AuthAlias()) {
		err = ErrApiAuthFail
	}
	return
}

// 组装唯一key
func key(tokenKey, token string) string {
	return fmt.Sprintf(constant.CacheAuth, tokenKey, token)
}
